// Copyright (C) 2025, ZWSOFT Co., LTD.(Guangzhou)
// Licensed under the MIT License. See LICENSE in the project root for license information.

using System.Collections.Concurrent;
using System.ComponentModel;
using System.Runtime.InteropServices;

namespace TestClient.Domain.Util;

partial class NativeLibrary : IDisposable
{
    #region Win32 api
    [LibraryImport("kernel32.dll", SetLastError = true, EntryPoint = "LoadLibraryA")]
    private static partial IntPtr LoadLibrary([MarshalAs(UnmanagedType.LPStr)] string path);

    [LibraryImport("kernel32.dll", SetLastError = true)]
    private static partial IntPtr GetProcAddress(IntPtr lib, [MarshalAs(UnmanagedType.LPStr)] string funcName);

    [LibraryImport("kernel32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static partial bool FreeLibrary(IntPtr lib);
    #endregion

    private IntPtr hLib;
    private bool disposedValue;
    private readonly ConcurrentDictionary<string, Delegate> _cachedFunctions = new();

    public NativeLibrary(string DLLPath)
    {
        hLib = LoadLibrary(DLLPath);
        if (hLib == IntPtr.Zero)
        {
            throw new Win32Exception(Marshal.GetLastWin32Error());
        }
    }

    public T GetFunction<T>(string name) where T : Delegate
    {
        ObjectDisposedException.ThrowIf(disposedValue, this);

        if (_cachedFunctions.TryGetValue(name, out var func))
        {
            if (func is T typedFunc)
            {
                return typedFunc;
            }
            throw new InvalidCastException(
                $"Cached delegate type {func.GetType().Name} " +
                $"does not match requested type {typeof(T).Name}");
        }

        IntPtr api = GetProcAddress(hLib, name);
        if (api == IntPtr.Zero)
        {
            throw new EntryPointNotFoundException($"Function '{name}' not found in library");
        }
        var newFunc = Marshal.GetDelegateForFunctionPointer<T>(api);
        _cachedFunctions[name] = newFunc;
        return newFunc;
    }

    #region Dispose
    protected virtual void Dispose(bool disposing)
    {
        if (!disposedValue)
        {
            if (disposing)
            {
                // Free managed things
                _cachedFunctions.Clear();
            }

            // Free unmanaged things
            if (hLib != IntPtr.Zero)
            {
                FreeLibrary(hLib);
                hLib = IntPtr.Zero;
            }
            // null on large fields
            // currently none

            disposedValue = true;
        }
    }

    ~NativeLibrary()
    {
        Dispose(disposing: false);
    }

    public void Dispose()
    {
        Dispose(disposing: true);
        GC.SuppressFinalize(this);
    }
    #endregion
}
